package service

import (
	"bufio"
	"encoding/json"
	"io"
	"net/http"
	"os/exec"
	"strconv"
	"time"

	"fmt"

	"github.com/labstack/echo"
	"github.com/uber-go/zap"
	valid "gopkg.in/asaskevich/govalidator.v4"
)

/* 执行cat、tail命令来传输部分需要的日志 */
type Command struct {
	Name string `json:"name"`
	Arg1 string `json:"arg1"`
	Arg2 string `json:"arg2"`
	Type string `json:"type"`
}

func logRevealStart() {
	e := echo.New()
	e.POST("/log/reveal", logReveal)
	e.Logger.Fatal(e.Start(":1324"))
}

func logReveal(c echo.Context) error {
	defer func() {
		if err := recover(); err != nil {
			Logger.Error("log reveal error", zap.Error(err.(error)), zap.Stack())
		}
	}()

	mainCmdS := c.FormValue("mainCmd")
	subCmdS1 := c.FormValue("subCmd1")
	subCmdS2 := c.FormValue("subCmd2")
	subCmdS3 := c.FormValue("subCmd3")
	app := c.FormValue("app")

	mainCmd := &Command{}
	subCmd1 := &Command{}
	subCmd2 := &Command{}
	subCmd3 := &Command{}
	err := json.Unmarshal([]byte(mainCmdS), &mainCmd)
	if err != nil {
		Logger.Warn("Main Command json decode error", zap.Error(err), zap.String("cmd", mainCmdS))
		return err
	}

	var commands []*Command
	if subCmdS1 != "" {
		err = json.Unmarshal([]byte(subCmdS1), &subCmd1)
		if err != nil {
			Logger.Warn("Sub Command json decode error", zap.Error(err), zap.String("cmd", subCmdS1))
			return err
		}
		commands = append(commands, subCmd1)
	}

	if subCmdS2 != "" {
		err = json.Unmarshal([]byte(subCmdS2), &subCmd2)
		if err != nil {
			Logger.Warn("Sub Command json decode error", zap.Error(err), zap.String("cmd", subCmdS2))
			return err
		}
		commands = append(commands, subCmd2)
	}

	if subCmdS3 != "" {
		err = json.Unmarshal([]byte(subCmdS3), &subCmd3)
		if err != nil {
			Logger.Warn("Sub Command json decode error", zap.Error(err), zap.String("cmd", subCmdS3))
			return err
		}
		commands = append(commands, subCmd3)
	}

	cmd, _ := genCmd(app, mainCmd, commands)

	c.Response().WriteHeader(http.StatusOK)
	command := exec.Command("/bin/sh", "-c", cmd)

	stdout, err := command.StdoutPipe()
	Logger.Debug("stream cmd", zap.String("cmd", cmd))
	if err != nil {
		Logger.Warn("stream cmd pipe error", zap.Error(err))
		return c.String(http.StatusOK, fmt.Sprintf("cmd pipe: %v, error : %v", cmd, err))
	}

	err = command.Start()
	if err != nil {
		Logger.Warn("stream cmd start error", zap.Error(err))
		return c.String(http.StatusOK, fmt.Sprintf("cmd start: %v, error : %v", cmd, err))
	}

	reader := bufio.NewReader(stdout)

	tm, _ := strconv.Atoi(c.FormValue("time"))

	if tm >= 60 || tm <= 0 {
		tm = 30
	}

	stop := time.After(time.Duration(tm) * time.Second)

	for {

		line, err := reader.ReadString('\n')
		if err != nil || io.EOF == err {
			goto STOP
		}
		c.Response().Write([]byte(line))
		c.Response().Flush()

		select {
		case <-stop:
			goto STOP
		default:
			continue
		}
	}
STOP:

	return nil
}

func genCmd(appName string, mainCmd *Command, subCmds []*Command) (string, bool) {
	var cmd string
	var stream bool
	lock.RLock()
	app := AppMap[appName]
	lock.RUnlock()
	if app == nil {
		Logger.Warn("genCmd error", zap.String("err", "app不存在"), zap.String("app", appName))
		return "", false
	}
	// mainCommand的Name只能为'tail'、'cat'
	// Arg2 必须为空
	// tail的Arg1只能为"",-f -Number，cat的Arg1只能为"",-n
	switch mainCmd.Name {
	case "tail":
		if mainCmd.Arg1 != "f" && !valid.IsNumeric(mainCmd.Arg1) {
			Logger.Warn("arg1 error", zap.String("main cmd", "tail"), zap.String("arg1", mainCmd.Arg1))
			return "", stream
		}

		if mainCmd.Arg1 == "f" {
			stream = true
		}
		isPath, _ := valid.IsFilePath(mainCmd.Arg2)
		if !isPath {
			Logger.Warn("arg2 error", zap.String("main cmd", "tail"), zap.String("arg2", mainCmd.Arg1))
			return "", stream
		}

		cmd = fmt.Sprintf("%s -%s %s ", mainCmd.Name, mainCmd.Arg1, mainCmd.Arg2)

	case "cat":
		if mainCmd.Arg1 != "" && mainCmd.Arg1 != "n" {
			Logger.Warn("arg1 error", zap.String("main cmd", "cat"), zap.String("arg1", mainCmd.Arg1))
			return "", stream
		}

		cmd = fmt.Sprintf("%s -n %s ", mainCmd.Name, mainCmd.Arg2)

	case "netstat":
		if mainCmd.Arg1 != "anp" {
			Logger.Warn("arg1 error", zap.String("main cmd", "netstat"), zap.String("arg1", mainCmd.Arg1))
			return "", stream
		}
	case "top":
		c1 := fmt.Sprintf("ps -ef | grep %s | grep -v 'grep ' | awk '{print $2}'", app.Name)
		cmd1 := exec.Command("/bin/sh", "-c", c1)

		bs, _ := cmd1.CombinedOutput()
		cmd = fmt.Sprintf("top -pid " + string(bs))
		stream = true
		return cmd, stream
	default:
		Logger.Warn("invalid command", zap.String("main cmd", mainCmd.Name), zap.String("arg1", mainCmd.Arg1))
		return cmd, stream
	}

	l := len(subCmds)
	if l == 0 {
		return cmd, stream
	}

	if mainCmd.Name == "tail" || mainCmd.Name == "cat" {
		//管道第二个命令可以是""或者grep

		for _, sub := range subCmds {
			if sub.Name != "grep" {
				Logger.Warn("invalid sub cmd", zap.String("main cmd", mainCmd.Name), zap.String("sub cmd", sub.Name))
				return "", stream
			}

			if sub.Arg1 != "" && sub.Arg1 != "v" {
				Logger.Warn("arg1 error", zap.String("main cmd", "netstat"), zap.String("sub cmd", sub.Name), zap.String("arg1", mainCmd.Arg1))
				return "", stream
			}

			if !valid.IsUTFLetterNumeric(sub.Arg2) {
				Logger.Warn("arg2 error", zap.String("main cmd", "netstat"), zap.String("sub cmd", sub.Name), zap.String("arg2", mainCmd.Arg2))
				return "", stream
			}

			if sub.Arg1 == "" {
				cmd = fmt.Sprintf("%s | grep  %s ", cmd, sub.Arg2)
			} else {
				cmd = fmt.Sprintf("%s | grep -%s %s ", cmd, sub.Arg1, sub.Arg2)
			}

			return cmd, stream
		}
	}

	if mainCmd.Name == "netstat" {
		for k, sub := range subCmds {
			if sub.Name == "grep" {
				if sub.Arg1 == "" {
					cmd = fmt.Sprintf("%s | grep  %s ", cmd, sub.Arg2)
					return cmd, stream
				}

				if sub.Arg1 == "v" {
					cmd = fmt.Sprintf("%s | grep -%s %s ", cmd, sub.Arg1, sub.Arg2)
					return cmd, stream
				}

				Logger.Warn("arg1 error", zap.String("main cmd", "netstat"), zap.String("sub cmd", sub.Name), zap.String("arg1", mainCmd.Arg1))
				return "", stream
			}

			// wc -l 必须在最后一个命令
			if k == l-1 && sub.Name == "wc" {
				if sub.Arg1 == "l" {
					cmd = fmt.Sprintf("%s | wc -l %s ", cmd, sub.Arg2)
					return cmd, stream
				}
				Logger.Warn("arg1 error", zap.String("main cmd", "netstat"), zap.String("sub cmd", sub.Name), zap.String("arg1", mainCmd.Arg1))
				return "", stream
			}

			Logger.Warn("netstat error", zap.Object("main cmd", mainCmd), zap.Object("sub cmd", sub))
			return "", stream
		}
	}

	return "", stream
}
